'''
dialogelements
Author: Alex Foran
'''

import gameroot,pygame,utility,loaders,pane

'''
A base for interactable elements to be displayed within a pane.
'''
class DialogElement:
	'''
	Constructor.
	
	Params:
	parent - A reference to the containing pane.
	x - The top left x of the element.
	y - The top left y of the element.
	'''
	def __init__(self,parent,x,y):
		self.parent=parent
		self.x=x
		self.y=y
	
	'''
	Updates the element. This method is to be overridden by extending classes.
	'''	
	def update(self):
		pass
	
	'''
	Draws the element. This method is to be overridden by extending classes.
	'''
	def paint(self):
		pass
	
	'''
	Returns a key of the element, or None if the element has no key. An element with a value is indexed by its key for form handling purposes.
	'''
	def getkey(self):
		return None
	
	'''
	Returns the selected value of the element, or None if the element has no value.
	'''
	def getvalue(self):
		return None

'''
A selectable menu item. Also the basis for any elements in a textual menu.
'''
class DialogMenuItem(DialogElement):
	'''
	Constructor.
	
	Params:
	parent - A reference to the containing pane.
	x - The top left x of the element.
	y - The top left y of the element.
	text - The text to display for the menu item.
	font - A pygame.font to use for rendering the text.
	indicator - A pygame.image object to draw to the left of the item when it has focus.
	color - The text color.
	function - The function to be called when the item is activated.
	'''
	def __init__(self,parent,x,y,text,font,indicator,color,function):
		DialogElement.__init__(self,parent,x,y)
		# obtain dimensions for drawing purposes
		testrender=font.render(text,True,color)
		self.width=testrender.get_width()
		self.height=testrender.get_height()
		self.font=font
		self.text=text
		self.indicator=indicator
		self.color=color
		self.function=function
	
	'''
	Update
	'''
	def update(self):
		monitor=self.parent.root.monitor
		# If this element is the current element...
		if self.parent.focusedelement==self:
			# if the item was clicked or the accept key was hit, activate the item
			if monitor.getjustpressed(self.parent.root.prefs["key_accept"]) or (self.mouseisover() and monitor.getjustpressed("MOUSE1")):
				self.function()
			elif self.parent.dialog==None:
				#control scrolling among elements
				if monitor.getjustpressed(self.parent.root.prefs["key_down"]):
					pos=utility.getarrayposition(self.parent.elements,self)+1
					while pos>=len(self.parent.elements):
						pos-=len(self.parent.elements)
					self.parent.givefocus(self.parent.elements[pos])
				if monitor.getjustpressed(self.parent.root.prefs["key_up"]):
					pos=utility.getarrayposition(self.parent.elements,self)-1
					while pos<0:
						pos+=len(self.parent.elements)
					self.parent.givefocus(self.parent.elements[pos])
		else:
			#check if the mouse has been moved (which is usually true if the cursor is visible, though this isn't really a good idea) to grant focus
			if self.mouseisover() and self.parent.cursorvisible:
				self.parent.givefocus(self)
	
	'''
	Returns whether the mouse is currently over this element.
	'''
	def mouseisover(self):
		monitor=self.parent.root.monitor
		return monitor.mousepos[0]>=self.x+20 and monitor.mousepos[0]<self.x+self.width+20 and monitor.mousepos[1]>=self.y and monitor.mousepos[1]<self.y+self.height
	
	'''
	Paint. Draws the menu item and the indicator.
	'''
	def paint(self):
		surface=pygame.display.get_surface()
		if self.parent.focusedelement==self:
			surface.blit(self.indicator,(self.x,self.y+(self.height-self.indicator.get_height())/2))
		surface.blit(self.font.render(self.text,True,self.color),(self.x+20,self.y))

'''
A menu item that toggles between multiple choices when clicked.
'''
class DialogClickSwitch(DialogMenuItem):
	'''
	Constructor.
	
	Params:
	parent - A reference to the containing pane.
	x - The top left x of the element.
	y - The top left y of the element.
	text - The text to display for the menu item.
	font - A pygame.font to use for rendering the text.
	indicator - A pygame.image object to draw to the left of the item when it has focus.
	color - The text color.
	key - The key for this switch.
	choices - An array of choices for this clickswitch.
	selected - The index of the default selection for this clickswitch. If not specified, the first item will be selected.
	'''
	def __init__(self,parent,x,y,text,font,indicator,color,key,choices,selected=0):
		# call super, substituting own function for callback
		DialogMenuItem.__init__(self,parent,x,y,text,font,indicator,color,self.incrementchoice)
		self.key=key
		self.choices=choices
		self.selected=selected
	
	'''
	Rotates to the next choice.
	'''
	def incrementchoice(self):
		self.selected+=1
		while self.selected>=len(self.choices):
			self.selected-=len(self.choices)
	
	'''
	Returns the key.
	'''
	def getkey(self):
		return self.key
	
	'''
	Returns the currently selected value.
	'''
	def getvalue(self):
		return self.choices[self.selected]
	
	'''
	Returns whether the mouse is currently over this item, including the (theoretical) size of the selection box.
	'''
	def mouseisover(self):
		monitor=self.parent.root.monitor
		offset=self.width
		#pad so the choices are aligned
		for element in self.parent.elements:
			if element.__class__==self.__class__:
				offset=max([offset,element.width])
		# 100 is a fair estimate for options width, I suppose... this should be done correctly at some point, though.
		return monitor.mousepos[0]>=self.x+20 and monitor.mousepos[0]<self.x+offset+40+100 and monitor.mousepos[1]>=self.y and monitor.mousepos[1]<self.y+self.height
	
	'''
	Paint.
	'''
	def paint(self):
		DialogMenuItem.paint(self)
		surface=pygame.display.get_surface()
		offset=0
		for element in self.parent.elements:
			if element.__class__==self.__class__:
				offset=max([offset,element.width])
		surface.blit(self.font.render(self.choices[self.selected],True,self.color),(self.x+40+offset,self.y))

'''
Single-line text input field.
'''
class DialogInputField(DialogMenuItem):
	'''
	Constructor.
	
	Params:
	parent - A reference to the containing pane.
	x - The top left x of the element.
	y - The top left y of the element.
	text - The text to display for the menu item.
	font - A pygame.font to use for rendering the text.
	indicator - A pygame.image object to draw to the left of the item when it has focus.
	color - The text color.
	key - The key for this switch.
	boxwidth - Specifies the width of the input box.
	value - The default value for the input field. If no value is specified, an empty string will be used.
	'''
	def __init__(self,parent,x,y,text,font,indicator,color,key,boxwidth,value=""):
		# call super, substituting own function for callback
		DialogMenuItem.__init__(self,parent,x,y,text,font,indicator,color,self.showinputdialog)
		self.key=key
		self.boxwidth=boxwidth
		self.setvalue(value)
	
	'''
	Displays a dialog to acquire text input.
	'''
	def showinputdialog(self):
		#calculate the correct offset for the box
		offset=self.width
		for element in self.parent.elements:
			if element.__class__==self.__class__:
				offset=max([offset,element.width])
		self.parent.dialog=DialogInputFieldDialog(self.parent.root,self,self.x+40+offset,self.y)
	
	'''
	Returns the key.
	'''
	def getkey(self):
		return self.key
	
	'''
	Returns the entered value.
	'''
	def getvalue(self):
		return self.value	
	
	'''
	Sets the value.
	
	Params:
	value - The value to put into the field.
	'''
	def setvalue(self,value):
		self.value=value
		teststr=""
		#calculate clipped version of string
		for char in value:
			if self.font.size(teststr+char)[0]>self.boxwidth:
				break
			teststr=teststr+char
		self.visiblevalue=teststr
	
	'''
	Returns whether the mouse is over the current element.
	'''
	def mouseisover(self):
		monitor=self.parent.root.monitor
		#account for box
		offset=self.width
		for element in self.parent.elements:
			if element.__class__==self.__class__:
				offset=max([offset,element.width])
		return monitor.mousepos[0]>=self.x+20 and monitor.mousepos[0]<self.x+offset+40+self.boxwidth and monitor.mousepos[1]>=self.y and monitor.mousepos[1]<self.y+self.height
	
	'''
	Paint.
	'''
	def paint(self):
		DialogMenuItem.paint(self)
		surface=pygame.display.get_surface()
		offset=self.width
		for element in self.parent.elements:
			if element.__class__==self.__class__:
				offset=max([offset,element.width])
		#draw box
		pygame.draw.rect(surface,self.color,pygame.Rect(self.x+38+offset,self.y-2,self.boxwidth+4,self.height+4),1)
		#don't draw the text when there's a dialog and this is the focused object (meaning that, most likely, a DialogInputFieldDialog is being shown)
		if self.parent.dialog is None or self!=self.parent.focusedelement:
			#draw only the text that fits into the box
			surface.blit(self.font.render(self.visiblevalue,True,self.color),(self.x+40+offset,self.y))

'''
Sneaky dialog to handle text input.
'''
class DialogInputFieldDialog(pane.Pane):
	'''
	Constructor.
	
	Params:
	root - A reference to the gameroot.
	field - The DialogInputField that is being entered into.
	x - The x-position of the box's upper left corner.
	y - The y-position of the box's upper left corner.
	'''
	def __init__(self,root,field,x,y):
		pane.Pane.__init__(self,root)
		self.field=field
		self.value=self.field.value
		self.x=x
		self.y=y
		#place carat at end
		self.caratlocation=len(self.value)
		self.blinktimer=0
	
	'''
	Update.
	'''
	def update(self):
		pane.Pane.update(self)
		monitor=self.root.monitor
		#if a key was just pressed...
		if monitor.lastpressedframe!=None:
			#if accept was pressed, set the field's value and close this dialog.
			if monitor.lastpressedframe==self.root.prefs["key_accept"]:
				self.field.setvalue(self.value)
				self.done=True
			#if cancel was pressed, discard this temporary value and close this dialog.
			elif monitor.lastpressedframe==self.root.prefs["key_cancel"]:
				self.done=True
			#if the user clicks outside of the box, treat like they hit accept.
			elif monitor.lastpressedframe=="MOUSE1":
				if not (self.x<=monitor.mousepos[0] and self.x+self.field.boxwidth>=monitor.mousepos[0] and self.y<=monitor.mousepos[1] and self.y+self.field.height>=monitor.mousepos[1]):
					self.field.setvalue(self.value)
					self.done=True
			#backspace
			elif monitor.lastpressedframe=="BACKSPACE":
				#can't back up if there's nothing there
				if self.caratlocation>0:
					self.value=self.value[0:self.caratlocation-1]+self.value[self.caratlocation:len(self.value)]
					self.caratlocation-=1
			#if a normal key or space was hit
			elif len(monitor.lastpressedframe)==1 or monitor.lastpressedframe=="SPACE":
				val=monitor.lastpressedframe
				#convert space so things don't get weird
				if val=="SPACE":
					val=" "
				#if the character is capital and shift isn't held, lowercase it
				if (ord(val)>=65 and ord(val)<=90) and not monitor.getstate("LEFT SHIFT") and not monitor.getstate("RIGHT SHIFT"):
					val=chr(ord(val)+32)
				self.value=self.value[0:self.caratlocation]+val+self.value[self.caratlocation:len(self.value)]
				self.caratlocation+=1
			#move carat left
			elif monitor.lastpressedframe=="LEFT":
				if self.caratlocation>0:
					self.caratlocation-=1
			#move carat right
			elif monitor.lastpressedframe=="RIGHT":
				if self.caratlocation<len(self.value):
					self.caratlocation+=1
	
	'''
	Paint.
	'''
	def repaint(self):
		surface=pygame.display.get_surface()
		pygame.draw.rect(surface,self.field.color,pygame.Rect(self.x-2,self.y-2,self.field.boxwidth+4,self.field.height+4),1)
		surface.blit(self.field.font.render(self.value,True,self.field.color),(self.x,self.y))
		#show the cursor every other 30 frames
		if self.blinktimer/30%2==1:
			offset=self.field.font.size(self.value[0:self.caratlocation])[0]
			surface.blit(self.field.font.render("|",True,self.field.color),(self.x+offset,self.y))
		#normally we wouldn't want to increment things in the repaint, but hey, it's just a cursor
		self.blinktimer+=1
		pane.Pane.repaint(self)

'''
Input field that captures a single key press.
'''
class DialogKeyEntry(DialogInputField):
	'''
	Constructor.
	
	Params:
	parent - A reference to the containing pane.
	x - The top left x of the element.
	y - The top left y of the element.
	text - The text to display for the menu item.
	font - A pygame.font to use for rendering the text.
	indicator - A pygame.image object to draw to the left of the item when it has focus.
	color - The text color.
	key - The key for this switch.
	boxwidth - Specifies the width of the input box.
	'''
	def __init__(self,parent,x,y,text,font,indicator,color,key,boxwidth):
		DialogInputField.__init__(self,parent,x,y,text,font,indicator,color,key,boxwidth,parent.root.prefs[key])
	
	'''
	Shows key entry dialog.
	'''
	def showinputdialog(self):
		offset=self.width
		for element in self.parent.elements:
			if element.__class__==self.__class__:
				offset=max([offset,element.width])
		self.parent.dialog=DialogKeyEntryDialog(self.parent.root,self,self.x+40+offset,self.y)

'''
Dialog to capture a single key press.
'''
class DialogKeyEntryDialog(pane.Pane):
	'''
	Constructor.
	
	Params:
	root - A reference to the gameroot.
	field - The KeyEntryField that is being entered into.
	x - The x-position of the box's upper left corner.
	y - The y-position of the box's upper left corner.
	'''
	def __init__(self,root,field,x,y):
		pane.Pane.__init__(self,root)
		self.field=field
		self.x=x
		self.y=y
	
	'''
	Update.
	'''
	def update(self):
		pane.Pane.update(self)
		monitor=self.root.monitor
		#if a key was pressed, set the field's value to that key and we can go home.
		if monitor.lastpressedframe!=None:
			self.field.setvalue(monitor.lastpressedframe)
			self.done=True
	
	'''
	Paint.
	'''
	def repaint(self):
		surface=pygame.display.get_surface()
		pygame.draw.rect(surface,self.field.color,pygame.Rect(self.x-2,self.y-2,self.field.boxwidth+4,self.field.height+4),1)
		#say <Press Key> until we're done
		surface.blit(self.field.font.render("<Press Key>",True,self.field.color),(self.x,self.y))
		pane.Pane.repaint(self)